﻿using System.Net.DnsToolkit.Packet;
using System.Collections.Generic;
using System.Net.DnsToolkit.Packet.Busy;

namespace System.Net.DnsToolkit.Serializer.Stream.Reader {

    public class MessageReader : IMessageReader<System.IO.Stream> {

        private readonly IHeaderReader<System.IO.Stream> _headerReader;
        private readonly IQuestionReader<System.IO.Stream> _questionReader;
        private readonly IRRReader<System.IO.Stream> _rrReader;

        public MessageReader(
            IHeaderReader<System.IO.Stream> headerReader, 
            IQuestionReader<System.IO.Stream> questionReader, 
            IRRReader<System.IO.Stream> rrReader) {

            if (headerReader == null) {
                throw new ArgumentNullException("headerReader");
            }
            if (questionReader == null) {
                throw new ArgumentNullException("questionReader");
            }
            if (rrReader == null) {
                throw new ArgumentNullException("rrReader");
            }

            _headerReader = headerReader;
            _questionReader = questionReader;
            _rrReader = rrReader;

        }

        public IMessage Read(System.IO.Stream source) {
            IMessage message;
            Read(false, source, out message);
            return message;
        }

        public bool TryRead(System.IO.Stream source, out IMessage message) {
            return Read(true, source, out message);
        }

        public bool Read(bool tryRead, System.IO.Stream source, out IMessage message) {

            if (source == null) {
                if (!tryRead) {
                    throw new ArgumentNullException("source");
                }
                message = null;
                return false;
            }

            IHeader header;
            var questions = new List<IQuestion>();
            var answers = new List<IRR>();
            var additionals = new List<IRR>();
            var authorities = new List<IRR>();

            if (!tryRead) {
                header = _headerReader.Read(source);
            } else {
                bool read = _headerReader.TryRead(source, out header);
                if (!read) {
                    message = null;
                    return false;
                }
            }
            

            int questionCount = header.QuestionCount;
            int answerCount = header.AnswerCount;
            int resourceCount = header.ResourceCount;
            int authorityCount = header.AuthorityCount;


            bool readQuestions = InternalQuestionRead(tryRead, questionCount, source, questions);
            if (!readQuestions) {
                message = null;
                return false;
            }

            bool readAnswers = InternalRRRead(tryRead, answerCount, source, answers);
            if (!readAnswers) {
                message = null;
                return false;
            }

            bool readResource = InternalRRRead(tryRead, resourceCount, source, additionals);
            if (!readResource) {
                message = null;
                return false;
            }

            bool readAuthorities = InternalRRRead(tryRead, authorityCount, source, authorities);
            if (!readAuthorities) {
                message = null;
                return false;
            }

            var m = new Message {
                Header = header,
                Questions = questions,
                Answers = answers,
                Additionals = additionals,
                Authorities = authorities

            };

            message = m;
            return true;
        }

        private bool InternalQuestionRead(bool tryRead, int count, System.IO.Stream source, List<IQuestion> qs) {

            var questions = new List<IQuestion>();
            for (int i = 0; i < count; i++) {
                IQuestion question;
                if (!tryRead) {
                    question = _questionReader.Read(source);
                }
                else {
                    bool read = _questionReader.TryRead(source, out question);
                    if (!read) {
                        return false;
                    }
                }
                questions.Add(question);
            }
            qs.AddRange(questions);
            return true;

        }

        private bool InternalRRRead(bool tryRead, int count, System.IO.Stream source, List<IRR> rrs) {

            var resources = new List<IRR>();
            for (int i = 0; i < count; i++) {
                IRR rr;
                if (!tryRead) {
                    rr = _rrReader.Read(source);
                }
                else {
                    bool read = _rrReader.TryRead(source, out rr);
                    if (!read) {
                        return false;
                    }
                }
                resources.Add(rr);
            }
            rrs.AddRange(resources);
            return true;

        }

    }
}
